package ferp.core.mode;

import ferp.core.card.Card;
import ferp.core.game.Game;
import ferp.core.game.Input;
import ferp.core.game.Settings;
import ferp.core.game.Trick;
import ferp.core.log.Log;
import ferp.core.player.Hand;
import ferp.core.player.Player;
import ferp.core.player.Profile;
import ferp.core.state.State;

/**
 * User: user
 * Date: 5/20/11 7:16 PM
 *
 * This is a base class for card modes: Misere, Passing, Tricking
 * It will provide some common functionality for its descendants
 */
public abstract class Moving extends Mode
{
  @Override
  public int initialize(Game.Listener listener, Profile profile, Game g, Settings settings) throws Game.Error
  {
    // common mode initialization
    int r = super.initialize(listener, profile, g, settings);

    // prepare 32 bit hash of dropped cards if any
    int dropReal = g.getDropHash(0) | g.getDropHash(1), dropGuessed = dropReal;

    // clear player trick counts
    for (Player p : g.players())
      g.player.tricks[p.id()] = 0;

    // clear trashed cards
    g.trash = 0;

    int h0 = g.getHumanHand().current;

    if (g.declarer() == g.human())
    {
      h0 = guessHumanHand(g);
      dropGuessed = (g.getHumanHand().current | dropReal) & ~h0;

      Log.debug(Log.TAG, "guessed human hand: " + Hand.dump(h0));
    }

    // request resolver initialization
    listener.onResolverInitialize(g, evaluator(), g.player.declarer, g.player.starting, g.trump().ordinal(),
                                  h0, g.getHand(1).current, g.getHand(2).current,
                                  g.getTalonCompressed(0), g.getTalonCompressed(1),
                                  dropReal, dropGuessed, g.type == Game.Type.UNKNOWN || g.type == Game.Type.OPEN);

    return r;
  }

  @Override
  public int chill(Game.Listener listener, Game g, Settings settings)
  {
    // fire event before the actual round end to show the trick until player interacts
    //listener.onBeforeMovingRoundEnd(g, mover(g, settings, g.player.current));

    Player mover = mover(g, settings, g.player.current);

    // manual game
    boolean manual = g.player.declarer == Game.NO_ID ||
        g.human() == g.declarer() ||
        !g.isOpen() ||
        !g.bid.isMisere() && g.getHumanBid().current.isWhist() ||
        g.bid.isMisere() && settings.misereMode != Settings.MisereMode.ANDROID;

    boolean tap;

    if (manual)
      tap = mover == g.human() ? settings.roundEndTapWhenLastMoverHuman : settings.roundEndTapWhenLastMoverAndroid;
    else
      tap = settings.roundEndTapWhenNotPlaying;

    if (tap)
    {
      g.options.set(Input.Options.WAIT_FOR_TOUCH);
      return State.Result.SHOW_OPTIONS;
    }

    if (manual && mover != g.human())
      return Settings.Delays.AFTER_CARD_PLAYED;

    return State.Result.SCHEDULE;
  }

  // get current mover (a player who plays a card)
  public Player mover(Game g, Settings settings, int current)
  {
    // by default it's a current player
    return g.player(current);
  }

  // get appropriate evaluator depending on the mode
  public abstract int evaluator();

  @Override
  public Card.State getTalonCardState(Game g, Settings settings, int i) {return Card.State.HIDDEN;}

  @Override
  protected void prepare(Game.Listener listener, Game g, Settings settings)
  {
    // fire moving round solve event
    listener.onMovingRoundStart();
  }

  @Override
  protected int options(Game.Listener listener, Game g, Settings settings, Input input) throws Game.Error
  {
    Player mover = mover(g, settings, g.player.current);

    g.options.set(Input.Options.WAIT_FOR_CARD_ANIMATION);

    // check for the obvious moves
    int candidates = Hand.getMoveCandidates(g, g.getHand(g.player.current).current);
    //boolean automatic = candidates.size() == 1 && ((mover != g.human() && g.type == Game.Type.OPEN) || (mover == g.human() && g.settings.automateObviousMoves));
    boolean automatic = Hand.size(candidates) == 1 && (mover == g.human() ? settings.automateObviousMoves : g.isOpen());

    if (automatic)
      listener.onPlayCard(g, g.current(), Card.card(Hand.first(candidates)));
    else
    {
      // set moving input options for the current player or opponent
      setMoverInputOptions(listener, g, mover);

      // solve the current position - must be called after setMovingInputOptions
      // since for human this callback disables candidates until tree is ready
      listener.onSolveRequested(g, mover);
    }

    return State.Result.SHOW_OPTIONS;
  }

  @Override
  protected Act act(Game.Listener listener, Game g, Settings settings, Input input) throws Game.Error
  {
    Player player = g.current();
    //Player mover = mover(g, player.id());

/*
    // if game type is open and current player is not a declarer - the actual "moving" player is an opponent
    Card card = move(g, mover, input);
*/

    // card returned by move() may be null in case resolver service is down
    if (input.card == 0)
      throw new Game.Error();

    // preform the actual move
    g.move(input.card, player);

    // continue resolve the "tree"
    //Resolver.advance(card);
    listener.onResolverAdvance(g, settings, input.card);

    return Act.NEXT;
  }

  @Override
  protected final void analyze(Game.Listener listener, Game g, Settings settings)
  {
    // round winner - the player who took the trick
    Player winner = g.player(Trick.winner(g.trick.current, g.trump()));

    // increment player's trick count
    g.addTricks(winner.id(), 1);
    // the winner of this round becomes a starter for the next one
    // getStarting Player is called in "initialize" only so the following will override
    g.player.starting = winner.id();

    // trash trick cards
    g.trash = g.trick.trash(g.trash);

    // fire moving round end event
    listener.onAfterMovingRoundEnd(g, settings, winner);

    // print player trick counts
    String out = "tricks: ";
    for (Player p : g.players())
      out += g.getTricks(p.id()) + " ";
    Log.debug(Log.TAG, out);
  }

  @Override
  protected boolean isSetOver(Game g)
  {
    // is the set over?
    return g.rounds == 10;
  }

  @Override
  protected void done(Game.Listener listener, Game g, Settings settings)
  {
    // switch to "score" mode
    g.score();
  }

  protected int guessHumanHand(Game g)
  {
    return g.getHumanHand().current;
  }

/*
  // perform the actual move in each descendant mode
  protected final Card move(Game g, Player mover, Input input)
  {
    if (automatic)
      return candidates.highest();

    // in case human plays the card take the played card from the input
    if (mover == g.human())
      return input.card;

    // otherwise request the best move
    return g.listener.onResolverGetBestMove(g.current.player.id());
  }
*/

  // set mover input options according to mode
  protected abstract void setMoverInputOptions(Game.Listener listener, Game g, Player mover) throws Game.Error;
}
